using System;
using System.Collections.Generic;
using System.IO;
using SQLRunnerLib.Project;
using SQLRunnerLib.Services;
using SQLServices.Log;
using SQLServices.TransMgr;
using System.Linq;
using SQLRunnerLib.Exceptions;

namespace SQLRunnerLib.Util
{
	public enum SessionLogLevel
	{
		Info,
		Error,
		None
	}

	[Flags]
	public enum ErrorIndicator
	{
		None = 0,
		AppFailure = 1,
		StopExecution = 2
	}

	/// <summary>
	/// Reads the command line arguments and start the application 
	/// command line arguments
	/// -p:[project file path]		Indicates the project file to use
	/// -s:[server name]			SQL Server instance
	/// -d:[database name]			Database
	/// -m:[o|p|s|c|r]              Connection Manager
	/// -t							Trusted connection, default value is TRUE
	/// -u:[user name]
	/// -w:[password]
	/// </summary>
	public class LineParser
	{
        public string ServerName { get; private set; }
        public string UserName { get; private set; }
        public string Password { get; private set; }
        public SessionLogLevel LogLevel { get; private set; }
        public string DatabaseName { get; private set; }
        public bool ShowHelp { get; private set; }
        public ErrorIndicator ErrorLevel { get; private set; }
        public ConnectionMgrType ConnMgrType { get; private set; }
        public bool? TrustedConnection { get; internal set; }
        public SQLProject Project { get; private set; }
        public bool IsProjectAvailable { get; private set; }
        public SQLEnvironment Environment { get; internal set; }

	    private readonly string[] Options = new[] {"h", "p:", "v:", "s:", "d:", "u:", "w:", "e:", "t", "m:"};

		public LineParser(IEnumerable<string> args)
		{
		    ConnMgrType = ConnectionMgrType.PessimistConnMgr;
		    LogLevel = SessionLogLevel.Error;
		    ErrorLevel = ErrorIndicator.None;
		    ShowHelp = false;
		    DatabaseName = string.Empty;
		    Password = string.Empty;
		    UserName = string.Empty;
		    ServerName = string.Empty;
            TrustedConnection = null;

            //Changed to work in a specific order because we need the project first.
			foreach (string option in Options) 
            {
		        foreach (string arg in args) 
                {
				    string value = string.Empty;
                    if (arg.StartsWith("-"))
                        // WI#11396 - Cannot connect to SQL Server if pwd is not set in lower case
                        value = arg.Substring(1); //.ToLower();
                    else
                        continue;

                    // WI#11396 - Cannot connect to SQL Server if pwd is not set in lower case
				    if (value.StartsWith(option, StringComparison.OrdinalIgnoreCase)) {
					    value = value.Substring(option.Length);
					    UpdateFlags(option, value);
					    break;
				    }
			    }
			}

            // Check for minimum requirement
            if (!this.ShowHelp && this.Project == null)
                throw new ValidationException("Minimum argument requirements have not been met.");
		}

	    private void UpdateFlags(string option, string value) {
			switch (option) {
				case "p:":
					DoProject(value);
					break;
				case "s:":
					ServerName = value;
					break;
				case "d:":
					DatabaseName = value;
					break;
				case "t":
					TrustedConnection = true;
					break;
				case "u:":
					UserName = value;
					break;
				case "w:":
					Password = value;
					break;
				case "h":
					ShowHelp = true;
					break;
				case "e:":
					DoError(value);
					break;
                case "m:":
			        DoConnMgr(value);
                    break;
                case "v:":
                    DoEnvironment(value);
                    break;
				default:
                    // This code is never called, even when invalid options are supplied.
                    ServiceContainer.Instance.Logger.LogMessage(string.Format(
						"The {0} option is invalid", option), MsgLevel.Error);
					throw new ApplicationException(string.Format(
						"The {0} option is invalid", option));
			}
		}

	    private void DoConnMgr(string value)
	    {
	        Dictionary<string, ConnectionMgrType> possibleValues = GetPossibleValues();
	        if (!possibleValues.ContainsKey(value.ToLower()))
                ServiceContainer.Instance.Logger.LogMessage(string.Format(
                    "The {0} option is invalid for a connection manager", value), MsgLevel.Error);
            else
	            this.ConnMgrType = possibleValues[value.ToLower()];
	    }

	    private Dictionary<string, ConnectionMgrType> GetPossibleValues()
	    {
	        Dictionary<string, ConnectionMgrType> possibleValues;
	        possibleValues = new Dictionary<string, ConnectionMgrType>();
	        possibleValues.Add("o", ConnectionMgrType.OptimisticConnMgr);
	        possibleValues.Add("p", ConnectionMgrType.PessimistConnMgr);
	        possibleValues.Add("s", ConnectionMgrType.StrictConnMgr);
	        possibleValues.Add("c", ConnectionMgrType.CommitTransactionConnMgr);
	        possibleValues.Add("r", ConnectionMgrType.RollbackTransactionConnMgr);
	        return possibleValues;
	    }

	    private void DoProject(string value) 
        {
			value = value.Replace("'", string.Empty).Replace("\"",string.Empty);
			FileInfo projFile = new FileInfo(value);
			if (!projFile.Exists) 
                throw new ValidationException(string.Format("Project file [{0}] does not exists", value));

            try 
            {
				Project = SQLProject.Deserialize(value);
				IsProjectAvailable = true;
			}
			catch 
            {
                throw new ValidationException(string.Format("Project file [{0}] could not be read", value));
			}
		}

        private void DoEnvironment(string value)
        {
            // Because we now control the order in which we process the arguments we know we have a Project at this point
            if (this.Project != null)
            {
                this.Environment = this.Project.Environments.FirstOrDefault(e => e.Name == value);
                if (this.Environment == null)
                    throw new ValidationException(string.Format("The environment name supplied [{0}] does not exist in given project file.", value));
            }
        }

		private void DoError(string value) 
        {
			ErrorIndicator errorvalue = ErrorIndicator.None;
			try 
            {
				int error = int.Parse(value);
				if ( ( error & (int) ErrorIndicator.AppFailure) == (int) ErrorIndicator.AppFailure)
					errorvalue = ErrorIndicator.AppFailure;
				
				if ( ( error & (int) ErrorIndicator.StopExecution) == (int) ErrorIndicator.StopExecution)
					errorvalue |= ErrorIndicator.StopExecution;
				
			}
			catch (Exception ex) 
            {
                ServiceContainer.Instance.Logger.LogMessage("Error parsing Error level: " + ex.Message, MsgLevel.Error);
			}
			ErrorLevel = errorvalue;
		}
	}
}
